package io.gitee.falllee.dao.util;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.stream.Collectors;

import org.apache.calcite.linq4j.Linq4j;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;

import io.gitee.falllee.common.util.LogUtils;
import io.gitee.falllee.common.util.SnowflakeUtils;
import io.gitee.falllee.dao.annotation.Column;
import io.gitee.falllee.dao.annotation.Table;
import io.gitee.falllee.dao.enums.JdbcTypeEnum;
import io.gitee.falllee.dao.enums.KeyTypeEnum;
import io.gitee.falllee.dao.model.DaoConst;
import io.gitee.falllee.dao.model.DaoException;

/**
 * @author batty
 * @date 2020/01/14
 */
public class DaoUtils {

    private static final String NULL = "null";

    private DaoUtils() {}

    /**
     * 获取Table
     * 
     * @param clazz
     * @return
     */
    public static <T> Table getTable(Class<T> clazz) {
        Table table = clazz.getAnnotation(Table.class);
        if (table == null && clazz.getSuperclass() != null) {
            table = clazz.getSuperclass().getAnnotation(Table.class);
        }
        if (table == null || !StringUtils.hasLength(table.value())) {
            throw new DaoException(
                String.format("Please add the 'Table' annotation on the '%s' Class", clazz.getName()));
        }
        return table;
    }

    /**
     * 获取表名
     * 
     * @param clazz
     * @return
     */
    public static <T> String getTableName(Class<T> clazz) {
        Table table = getTable(clazz);
        return table.value();
    }

    public static <T> String getKeyField(Class<T> clazz) {
        Method method = DaoUtils.getKeyGetMethod(clazz);
        return method.getAnnotation(Column.class).value();
    }

    /**
     * 根据方法名获取属性名
     * 
     * @param methodName
     * @return
     */
    public static String getFieldNameByMethodName(String methodName) {
        return String.format("%s%s", methodName.substring(3, 4).toLowerCase(), methodName.substring(4));
    }
    
    /**
     * 根据字段获取方法
     * 
     * @param clazz
     * @param columnValue
     * @return
     */
    public static <T> Method getMethodByColumnValue(Class<T> clazz, String columnValue) {
        Method method = Linq4j.asEnumerable(clazz.getMethods()).singleOrDefault(
            p -> p.getAnnotation(Column.class) != null && columnValue.equalsIgnoreCase(p.getAnnotation(Column.class).value()));
        if (method == null) {
            throw new DaoException(String.format("Please set value '%s' to the 'Column' annotation on the '%s' Class",
                columnValue, clazz.getName()));
        }
        return method;
    }

    /**
     * 根据字段获取Set方法
     * 
     * @param clazz
     * @param columnValue
     * @return
     */
    public static <T> Method getSetMethodByColumnValue(Class<T> clazz, String columnValue) {
        Method method = Linq4j.asEnumerable(clazz.getMethods()).singleOrDefault(
            p -> p.getAnnotation(Column.class) != null && columnValue.equalsIgnoreCase(p.getAnnotation(Column.class).value()));
        if (method == null) {
            throw new DaoException(String.format("Please set value '%s' to the 'Column' annotation on the '%s' Class",
                columnValue, clazz.getName()));
        }
        String setMethodName = method.getName().replaceFirst("get", "set");
        return Linq4j.asEnumerable(clazz.getMethods()).singleOrDefault(p -> p.getName().equals(setMethodName));
    }

    /**
     * 获取方法的值
     * 
     * @param t
     * @param method
     * @param clazz
     * @return
     */
    public static <T, V> V getValue(T t, Method method, Class<V> clazz) {
        try {
            if (t == null || method == null) {
                return null;
            }
            Object obj = method.invoke(t);
            if (obj == null) {
                return null;
            }
            return clazz.cast(obj);
        } catch (Exception e) {
            Map<String, Object> paramMap = new HashMap<>(16);
            paramMap.put("t", t);
            paramMap.put("method", method.getName());
            paramMap.put("clazz", clazz);
            LogUtils.logException(paramMap, e);
            return null;
        }
    }

    public static <T> Object getValue(T t, Method method) {
        if (method == null || method.getReturnType() == null) {
            return null;
        }
        return getValue(t, method, method.getReturnType());
    }

    /**
     * 设置值
     * 
     * @param t
     * @param method
     * @param obj
     */
    public static <T> void setValue(T t, Method method, Object obj) {
        try {
            if (t == null || method == null) {
                return;
            }
            if (ObjectUtils.isEmpty(method.getParameterTypes())) {
                return;
            }
            Class<?> clazz = method.getParameterTypes()[0];
            Object result = null;
            if (obj != null) {
                result = ConvertUtils.string2Object(obj.toString(), clazz);
            }
            method.invoke(t, result);
        } catch (Exception e) {
            Map<String, Object> paramMap = new HashMap<>(16);
            paramMap.put("t", t);
            paramMap.put("method", method.getName());
            paramMap.put("obj", obj);
            LogUtils.logException(paramMap, e);
        }
    }

    /**
     * 获取主键的getMethod
     * 
     * @param clazz
     * @return
     */
    public static <T> Method getKeyGetMethod(Class<T> clazz) {
        Method method = Linq4j.asEnumerable(clazz.getMethods())
            .singleOrDefault(p -> p.getAnnotation(Column.class) != null && p.getAnnotation(Column.class).key());
        if (method == null) {
            throw new DaoException(
                String.format("Please set key to the 'Column' annotation on the '%s' Class", clazz.getName()));
        }
        return method;
    }
    
    public static <T> Class<?> getKeyValueType(Class<T> clazz) {
        Method method = getKeyGetMethod(clazz);
        return method.getReturnType();
    }
    
    /**
     * 获取主键值
     * 
     * @param t
     * @return
     */
    public static <T> Object getKeyValue(T t) {
        Class<?> clazz = t.getClass();
        Method keyGetMethod = getKeyGetMethod(clazz);
        return getValue(t, keyGetMethod);
    }
    
    /**
     * 获取主键值列表
     * 
     * @param list
     * @return
     */
    public static <T> List<Object> getKeyValueList(List<T> list) {
        if (CollectionUtils.isEmpty(list)) {
            return new ArrayList<>();
        }
        Class<?> clazz = list.get(0).getClass();
        Method keyGetMethod = getKeyGetMethod(clazz);
        List<Object> keyValueList = new ArrayList<>();
        for (T t : list) {
            Object val = getValue(t, keyGetMethod);
            if (!ObjectUtils.isEmpty(val)) {
                keyValueList.add(val);
            }
        }
        return keyValueList;
    }
    
    public static List<Object> getKeyValueList(List<Map<String, Object>> list, String keyField) {
        return list.stream().map(p -> p.get(keyField)).collect(Collectors.toList());
    }
    
    /**
     * 设置主键值
     * 
     * @param t
     * @param keyValue
     */
    public static <T> void setKeyValue(T t, Object keyValue) {
        Class<?> clazz = t.getClass();
        Method keySetMethod = getKeySetMethod(clazz);
        setValue(t, keySetMethod, keyValue);
    }

    /**
     * 获取主键setMethod
     * 
     * @param clazz
     * @return
     */
    public static <T> Method getKeySetMethod(Class<T> clazz) {
        Method method = Linq4j.asEnumerable(clazz.getMethods())
            .singleOrDefault(p -> p.getAnnotation(Column.class) != null && p.getAnnotation(Column.class).key());
        if (method == null) {
            throw new DaoException(
                String.format("Please set key to the 'Column' annotation on the '%s' Class", clazz.getName()));
        }
        String setMethodName = method.getName().replaceFirst("get", "set");
        return Linq4j.asEnumerable(clazz.getMethods()).singleOrDefault(p -> p.getName().equals(setMethodName));
    }

    /**
     * 获取排序的Method列表(按Column注解的order()排序)
     * 
     * @param clazz
     * @return
     */
    public static <T> List<Method> getOrderMethods(Class<T> clazz) {
        List<Method> methods = Linq4j.asEnumerable(clazz.getMethods()).where(p -> {
            Column col = p.getAnnotation(Column.class);
            return col != null && StringUtils.hasLength(col.value());
        }).toList();
        int legth = Linq4j.asEnumerable(methods).where(p -> p.getAnnotation(Column.class).order() > 0).count();
        int i = legth + 1;
        methods = Linq4j.asEnumerable(methods).orderBy(p -> {
            int order = p.getAnnotation(Column.class).order();
            return order > 0 ? order : i;
        }).toList();
        return methods;
    }

    /**
     * 获取排序的Column列表(按order()排序)
     * 
     * @param clazz
     * @return
     */
    public static <T> List<Column> getOrderColumns(Class<T> clazz) {
        List<Column> columns = Linq4j.asEnumerable(clazz.getMethods()).where(p -> {
            Column col = p.getAnnotation(Column.class);
            return col != null && StringUtils.hasLength(col.value());
        }).select(p -> p.getAnnotation(Column.class)).toList();
        int legth = Linq4j.asEnumerable(columns).where(p -> p.order() > 0).count();
        int i = legth + 1;
        columns = Linq4j.asEnumerable(columns).orderBy(p -> {
            return p.order() > 0 ? p.order() : i;
        }).toList();
        return columns;
    }
    
    public static <T> int getSyncDatePrecision(Class<T> clazz) {
        Table table = getTable(clazz);
        return table.syncDatePrecision().getPrecision();
    }

    /**
     * 设置固定字段的值
     * 
     * @param <T>
     * @param fixFieldValue key:字段列名;value:字段值
     * @param t
     */
    public static <T> void setFixFieldValue(Map<String, Object> fixFieldValue, T t) {
        if (ObjectUtils.isEmpty(fixFieldValue) || t == null) {
            return;
        }
        Iterator<Entry<String, Object>> iterator = fixFieldValue.entrySet().iterator();
        while (iterator.hasNext()) {
            Entry<String, Object> entry = iterator.next();
            String fieldName = entry.getKey();
            // 如果实体存在值优先取实体的值，实体没值则从请求头取
            Method method = getMethodByColumnValue(t.getClass(), fieldName);
            Object val = getValue(t, method);
            if (!ObjectUtils.isEmpty(val)) {
                continue;
            }
            val = entry.getValue();
            if (ObjectUtils.isEmpty(val) || NULL.equals(val.toString())) {
                continue;
            }
            try {
                method = getSetMethodByColumnValue(t.getClass(), fieldName);
                setValue(t, method, val);
            } catch (DaoException ex) {
                // 屏蔽异常
                LogUtils.logWarn(ex.getMessage());
            }
        }
    }
    
    public static void setFixFieldValue(Map<String, Object> fixFieldValue, Map<String, Object> map) {
        if (ObjectUtils.isEmpty(fixFieldValue) || ObjectUtils.isEmpty(map)) {
            return;
        }
        Iterator<Entry<String, Object>> iterator = fixFieldValue.entrySet().iterator();
        while (iterator.hasNext()) {
            Entry<String, Object> entry = iterator.next();
            String columnName = entry.getKey();
            // 如果map存在值优先取实体的值，map没值则从请求头取
            Object val = map.get(columnName);
            if (!ObjectUtils.isEmpty(val)) {
                continue;
            }
            val = entry.getValue();
            if (ObjectUtils.isEmpty(val)) {
                continue;
            }
            map.put(columnName, val);
        }
    }
    
    public static Object getParamValue(String val, Class<?> clazz) {
        Object objVal = null;
        if (clazz.equals(Date.class)) {
            objVal = val;
        } else {
            objVal = ConvertUtils.string2Object(val, clazz);
        }
        return objVal;
    }
    
    public static String getJdbcType(Class<?> dataType, JdbcTypeEnum jdbcType) {
        if (jdbcType.equals(JdbcTypeEnum.DEFAULT)) {
            return ConvertUtils.java2JdbcType(dataType);
        } else {
            return jdbcType.getJdbcType();
        }
    }

    public static Object generateKeyValue(String keyType, Object objVal) {
        if (ObjectUtils.isEmpty(objVal)) {
            if (KeyTypeEnum.UUID.getType().equals(keyType)) {
                objVal = CommonUtils.getGuid();
            } else if (KeyTypeEnum.SNOWFLAKE_ID.getType().equals(keyType)) {
                objVal = SnowflakeUtils.nextId();
            }
        } else if (keyType.equals(KeyTypeEnum.AUTO.getType())) {
            objVal = null;
        }
        return objVal;
    }

    public static Method getMethod(Class<? extends Object> clazz, String getMethodName) {
        try {
            return clazz.getMethod(getMethodName);
        } catch (NoSuchMethodException | SecurityException e) {
            throw new DaoException(String.format("%s error", getMethodName), e);
        }
    }
    
    public static Class<?>[] listClass2Array(List<Class<?>> list) {
        Class<?>[] pars;
        if (!CollectionUtils.isEmpty(list)) {
            pars = new Class<?>[list.size()];
            for(int i = 0; i< list.size(); i++) {
                pars[i] = list.get(i);
            }
        } else {
            pars = new Class<?>[0];
        }
        return pars;
    }
    
    public static Object getEnableFlagValue(String val) {
        if (val.equals("1") || val.equals("0")) {
            return Integer.valueOf(val);
        } else if (val.equalsIgnoreCase("true") || val.equalsIgnoreCase("false")) {
            return Boolean.valueOf(val);
        }
        return val;
    }

    /**
     * 获取请求头固定字段key
     * 
     * @param headerParamKey
     * @return
     */
    public static String[] getFixHeaderParamKey(String headerParamKey) {
        String[] arr = headerParamKey.split(DaoConst.COMMA);
        int i = 0;
        for (String key : arr) {
            if (key.contains(DaoConst.COLON)) {
                arr[i] = key.split(DaoConst.COLON)[0].trim();
            }
            i++;
        }
        return arr;
    }
    
    /**
     * 获取请求头固定字段key和DB字段映射
     * 
     * @param headerParamKey
     * @return
     */
    public static Map<String, String> getFixHeaderParamKeyColumnMap(String headerParamKey) {
        Map<String, String> map = new HashMap<>(16);
        String[] arr = headerParamKey.split(DaoConst.COMMA);
        for (String key : arr) {
            String columnName;
            // 如果key包含:,则字段取:后的内容，否则按key驼峰转换下划线
            if (key.contains(DaoConst.COLON)) {
                String[] keyArr = key.split(DaoConst.COLON);
                key = keyArr[0].trim();
                columnName = keyArr[1].trim();
            } else {
                key = key.trim();
                columnName = ConvertUtils.hump2Underline(key);
            }
            map.put(key, columnName);
        }
        return map;
    }

    public static String[] list2Array(List<String> params) {
        if (CollectionUtils.isEmpty(params)) {
            return new String[0];
        }
        return params.toArray(new String[] {});
    }
}
